package com.huilian.iotbox.client.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.huilian.iotbox.client.security.enums.JwtRedisEnum;
import com.huilian.iotbox.client.security.jwt.util.JwtTokenUtil;
import com.huilian.iotbox.client.security.properties.SecurityProperties;
import com.huilian.iotbox.client.service.*;
import com.huilian.iotbox.data.common.RedisKey;
import com.huilian.iotbox.data.common.ServerResponse;
import com.huilian.iotbox.data.dto.MemberStoreBalanceDto;
import com.huilian.iotbox.data.dto.WeiXinDto;
import com.huilian.iotbox.data.enums.ApiParamEnum;
import com.huilian.iotbox.data.enums.DeviceStatusEnum;
import com.huilian.iotbox.data.enums.DeviceTypeEnum;
import com.huilian.iotbox.data.enums.MemberStatusEnum;
import com.huilian.iotbox.data.po.Agency;
import com.huilian.iotbox.data.po.AppletOrOfficialAccount;
import com.huilian.iotbox.data.po.Member;
import com.huilian.iotbox.data.properties.ConfigProperties;
import com.huilian.iotbox.data.properties.ImageProperties;
import com.huilian.iotbox.data.service.AppletOrOfficialAccountCommonService;
import com.huilian.iotbox.data.service.ErrorLogCommonService;
import com.huilian.iotbox.data.service.MemberStoreBalanceCommonService;
import com.huilian.iotbox.data.utils.Util;
import com.huilian.iotbox.data.vo.DeviceVo;
import com.huilian.iotbox.data.vo.MemberStoreBalanceVo;
import com.huilian.iotbox.data.vo.MemberVo;
import com.huilian.tool.applet.wechat.service.WeChatAppletService;
import com.huilian.tool.common.config.WeixinConfig;
import com.huilian.tool.common.utils.HttpServiceUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class WeChatServiceImpl implements WeChatService {

    @Autowired
    private WeixinConfig weixinConfig;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private AgencyService agencyService;
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private MemberService memberService;
    @Autowired
    private ImageProperties imageProperties;
    @Autowired
    public ConfigProperties configProperties;
    @Autowired
    private SecurityProperties securityProperties;
    @Autowired
    private WeChatAppletService weChatAppletService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ErrorLogCommonService errorLogCommonService;
    @Autowired
    private MemberStoreBalanceService memberStoreBalanceService;
    @Autowired
    private RedisTemplate<String, Serializable> redisCacheTemplate;
    @Autowired
    private MemberStoreBalanceCommonService memberStoreBalanceCommonService;
    @Autowired
    private AppletOrOfficialAccountCommonService appletOrOfficialAccountCommonService;

    /**
     * 微信通过code换取网页授权access_token
     */
    private final String URL_GET_WEIXIN_OPENID = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";

    /**
     * 扫设备码先到这里，然后重定向请求中间件获取用户信息，再重定向回下个接口"/biz/login"
     *
     * @param params
     * @param model
     * @param request
     * @return
     * @throws UnsupportedEncodingException
     */
    @Override
    public synchronized String biz(String params, Model model, HttpServletRequest request) {
        try {

            /**
             * attach[0]:设备号（deviceNo），必须
             * attach[1]:接口类型，必须，ApiParamEnum
             * attach[2]:接口参数，非必须
             * ···  非必须
             */

            log.info("params：{}", params);
            final String encodeBeforeParams = URLEncoder.encode(params, "UTF-8");
            log.info("encodeBeforeParams：{}", encodeBeforeParams);

            if (params.contains("_")) {
                params = params.replace("_", "^");
            }
            String[] paramArray = params.split("\\^");
            log.info("paramArray:{}", Arrays.toString(paramArray));

            Integer paramCode = Integer.parseInt(paramArray[1]);
            log.info("paramCode:{}", paramCode);

            final String deviceNo = paramArray[0];
            final DeviceVo deviceVo = deviceService.findOneDeviceByDeviceNo(deviceNo);

            String routeUrl = configProperties.getClientBackEndUrl();
            String appId = weixinConfig.getAppId();
            Integer appletOrOfficialAccountId = null;
            if (!StringUtils.isEmpty(deviceVo.getAgencyId())) {
                final Agency agency = agencyService.getAgencyById(deviceVo.getAgencyId());
                if (!StringUtils.isEmpty(agency) && !StringUtils.isEmpty(agency.getDedicatedOfficialAccount())) {
                    // 合作伙伴私有化部署
                    final AppletOrOfficialAccount appletOrOfficialAccount = appletOrOfficialAccountCommonService.findOneById(agency.getDedicatedOfficialAccount());
                    if (!StringUtils.isEmpty(appletOrOfficialAccount)) {
                        appId = appletOrOfficialAccount.getOfficialAccountAppId();
                        routeUrl = appletOrOfficialAccount.getOfficialAccountUrl() + "/client";
                        appletOrOfficialAccountId = appletOrOfficialAccount.getId();
                    }
                }
            }
            final String redirectUrl = routeUrl + "/api/biz/login";
            log.info("appId:{}", appId);
            log.info("redirectUrl:{}", redirectUrl);
            log.info("appletOrOfficialAccountId:{}", appletOrOfficialAccountId);

            String userAgent = request.getHeader("User-Agent").toLowerCase();
            if (userAgent.contains("alipayclient")) {
                log.info("通过支付宝获取用户信息");
                final String HUI_USERINFO_URL = "https://open.huilianpay.com/output/userInfo?redirectUrl=%s&attach=%s";
                String url = String.format(HUI_USERINFO_URL, redirectUrl, encodeBeforeParams);
                return "redirect:" + url;
            } else if (userAgent.contains("micromessenger")) {
                log.info("通过微信获取用户信息");
                final String outTradeNo = Util.getOutTradeNo();
                log.info("outTradeNo:{}", outTradeNo);
                String myRedirectUrl = String.format("%s/api/getOpenIdByOpen", routeUrl);

                StringBuilder no = new StringBuilder(outTradeNo);
                if (!StringUtils.isEmpty(appletOrOfficialAccountId)) {
                    no.append("^");
                    no.append(appletOrOfficialAccountId);
                }
                myRedirectUrl = myRedirectUrl + "?no=" + URLEncoder.encode(no.toString(), "UTF-8");

                log.info("myRedirectUrl:{}", myRedirectUrl);
                myRedirectUrl = URLEncoder.encode(myRedirectUrl, "UTF-8");

                final String scope = "snsapi_base";
                String userRedirectUrl = redirectUrl + "?attach=" + encodeBeforeParams;
                log.info("userRedirectUrl:{} ", userRedirectUrl);

                userRedirectUrl = URLEncoder.encode(userRedirectUrl, "UTF-8");

                String URL_GET_WEIXIN_AUTH_CODE = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&connect_redirect=1&forcePopup=true&forceSnapShot=false#wechat_redirect";
                final String url = String.format(URL_GET_WEIXIN_AUTH_CODE, appId, myRedirectUrl, scope, userRedirectUrl);
                log.info("请求获取OpenId的回调地址:{},附加参数:{}: ", userRedirectUrl, encodeBeforeParams);
                return "redirect:" + url;
            } else {
                model.addAttribute("msg", "请使用微信或支付宝APP扫码");
                return "error";
            }
        } catch (Exception e) {
            e.printStackTrace();
            errorLogCommonService.writerInfoException("登录失败_1", params, e);
            model.addAttribute("msg", "登录失败_1，失败原因：" + e.getMessage());
            return "error";
        }
    }


    /**
     * 通过开放平台获取微信openId
     *
     * @param request
     * @param model
     * @return
     * @throws UnsupportedEncodingException
     */
    @Override
    public synchronized String getOpenIdByOpen(HttpServletRequest request, Model model) throws UnsupportedEncodingException {
        try {
            //微信返回的授权码
            final String code = request.getParameter("code");

            //请求用户的回调地址
            final String redirectUrl = request.getParameter("state");

            //唯一标识，一般用来查日志用
            final String no = request.getParameter("no");
            log.info("no：{}", no);

            //专属公众号ID
            String appletOrOfficialAccountIdStr = null;
            if (!StringUtils.isEmpty(no)) {
                final String decodeBeforeNo = URLDecoder.decode(no, "utf-8");
                String[] noArray = decodeBeforeNo.split("\\^");
                if (!StringUtils.isEmpty(noArray) && noArray.length >= 1) {
                    String outTradeNo = noArray[0];
                    log.info("outTradeNo：{}", outTradeNo);
                    if (noArray.length == 2) {
                        appletOrOfficialAccountIdStr = noArray[1];
                    }
                }
            }

            log.info("appletOrOfficialAccountIdStr：{}", appletOrOfficialAccountIdStr);

            String appId = weixinConfig.getAppId();
            String appSecret = weixinConfig.getAppSecret();

            Integer appletOrOfficialAccountId = null;
            if (!StringUtils.isEmpty(appletOrOfficialAccountIdStr)) {
                appletOrOfficialAccountId = Integer.valueOf(appletOrOfficialAccountIdStr);
                AppletOrOfficialAccount appletOrOfficialAccount = appletOrOfficialAccountCommonService.findOneById(appletOrOfficialAccountId);
                if (!StringUtils.isEmpty(appletOrOfficialAccountId)) {
                    appId = appletOrOfficialAccount.getOfficialAccountAppId();
                    appSecret = appletOrOfficialAccount.getOfficialAccountAppSecret();
                    log.info("getOpenIdByOpen命中专属公众号");
                }
            }
            log.info("appId：{}", appId);

            final String reqUrl = String.format(URL_GET_WEIXIN_OPENID, appId, appSecret, code);
            /**
             * {
             *   "access_token": "81_VyWn2rQAlDyUyB_c00NyT1FrwLWC-YWTC7aAkjB7sOmI66E9AsQ9s2viqM4FrlS04gbhyHj-_ywbPgrRJTzHfJRO0hWDbrbEhWgYXczjG58",
             *   "expires_in": 7200,
             *   "refresh_token": "81_YDdHhsUWwkXkfHcG3S2I8RwdLbwlbJtDpm19Bp_m5v8i9YIC_CpQA4fS7lP2qSQjDKJqT2OFhmSMisz9mF2Ck9-tG2r9ZxAtG5laXC1pO80",
             *   "openid": "oufus5_Ie8umWrV7H_mBhkav6sF4",
             *   "scope": "snsapi_base"
             * }
             */
            log.info("reqUrl:" + reqUrl);
            final String result = HttpServiceUtil.httpGet(reqUrl);
            log.info("result:" + result);
            JSONObject jsonObject = JSON.parseObject(result);
            final String openId = jsonObject.getString("openid");
            if (!StringUtils.isEmpty(openId)) {
                JSONObject jsonObject1 = new JSONObject();
                jsonObject1.put("appId", appId);
                jsonObject1.put("appSecret", appSecret);
                redisCacheTemplate.opsForValue().set(String.format(RedisKey.OPENID_GET_APPID, openId), jsonObject1, 1, TimeUnit.HOURS);
            }

            StringBuffer url = new StringBuffer(redirectUrl);
            url.append("&userInfo=").append(URLEncoder.encode(result, "UTF-8"));

            return "redirect:" + url;
        } catch (Exception e) {
            e.printStackTrace();
            errorLogCommonService.writerInfoException("登录失败_2", null, e);
            model.addAttribute("msg", "登录失败_2，失败原因：" + e.getMessage());
            return "error";
        }
    }

    @Override
    public synchronized String getMemberInfo(String deviceNo, String params, String officialAccountOpenId, Model model, HttpServletRequest request) {
        try {
            log.info("params：{}", params);
            log.info("officialAccountOpenId：{}", officialAccountOpenId);

            DeviceVo deviceVo = deviceService.findOneDeviceByDeviceNo(deviceNo);
            String routeUrl = configProperties.getClientBackEndUrl();
            if (!StringUtils.isEmpty(deviceVo.getAgencyId())) {
                final Agency agency = agencyService.getAgencyById(deviceVo.getAgencyId());
                if (!StringUtils.isEmpty(agency) && !StringUtils.isEmpty(agency.getDedicatedOfficialAccount())) {
                    // 合作伙伴私有化部署
                    final AppletOrOfficialAccount appletOrOfficialAccount = appletOrOfficialAccountCommonService.findOneById(agency.getDedicatedOfficialAccount());
                    if (!StringUtils.isEmpty(appletOrOfficialAccount)) {
                        routeUrl = appletOrOfficialAccount.getOfficialAccountUrl() + "/client";
                    }
                }
            }

            String userAgent = request.getHeader("User-Agent").toLowerCase();
            String url = "";
            if (!userAgent.contains("micromessenger")) {
                model.addAttribute("msg", "请使用微信扫码");
                return "error";
            }

            if (StringUtils.isEmpty(officialAccountOpenId)) {
                errorLogCommonService.writerInfoErrorLog("会员openId不能为空，请重新扫码", officialAccountOpenId);
                model.addAttribute("msg", "会员openId不能为空，请重新扫码");
                return "error";
            }

            StringBuilder baseUrl = new StringBuilder(routeUrl);

            log.info("通过微信开放平台获取用户信息");
            StringBuilder myRedirectUrl = new StringBuilder(baseUrl);
            myRedirectUrl.append("/api/weixin/outPutUserInfoByOpenId");
            myRedirectUrl.append("?officialAccountOpenId=");
            myRedirectUrl.append(officialAccountOpenId);
            log.info("myRedirectUrl：{}", myRedirectUrl);

            final String myRedirectUrlEncodeBefore = URLEncoder.encode(myRedirectUrl.toString(), "UTF-8");

            final String encodeBeforeParams = URLEncoder.encode(params, "UTF-8");
            log.info("encodeBeforeParams：{}", encodeBeforeParams);

            String appId = weixinConfig.getAppId();
            final String key = String.format(RedisKey.OPENID_GET_APPID, officialAccountOpenId);
            JSONObject jsonObject = (JSONObject) redisCacheTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(jsonObject)) {
                String appIdByIn = jsonObject.getString("appId");
                if (!StringUtils.isEmpty(appIdByIn)) {
                    appId = appIdByIn;
                }
            }

            String redirectUrl = baseUrl + "/api/biz/login";
            log.info("redirectUrl：{}", redirectUrl);

            //用户传递进来，获取到Code后回调地址，将他放到state字段带到请求中，微信会原样返回
            final String userRedirectUrl = redirectUrl + "?attach=" + encodeBeforeParams;
            log.info("请求获取userInfo的回调地址：{}", userRedirectUrl);

            final String userRedirectUrlEncodeBefore = URLEncoder.encode(userRedirectUrl, "UTF-8");
            log.info("userRedirectUrlEncodeBefore：{}", userRedirectUrlEncodeBefore);

            final String scope = "snsapi_userinfo";
            final String URL_GET_WEIXIN_AUTH_CODE = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&connect_redirect=1&forcePopup=true&forceSnapShot=false#wechat_redirect";
            url = String.format(URL_GET_WEIXIN_AUTH_CODE, appId, myRedirectUrlEncodeBefore, scope, userRedirectUrlEncodeBefore);
            log.info("url：{}:", url);

            return "redirect:" + url;
        } catch (Exception e) {
            e.printStackTrace();
            errorLogCommonService.writerInfoException("登录失败_3", null, e);
            model.addAttribute("msg", "登录失败_3，失败原因：" + e.getMessage());
            return "error";
        }
    }


    @Override
    public synchronized String outPutUserInfoByOpenId(HttpServletRequest request, Model model) {

        try {
            final String officialAccountOpenId = request.getParameter("officialAccountOpenId");
            String redirectUrl = request.getParameter("state");
            String code = request.getParameter("code");
            log.info("officialAccountOpenId: {}: ", officialAccountOpenId);
            log.info("redirectUrl: {}:", redirectUrl);

            String appId = weixinConfig.getAppId();
            String appSecret = weixinConfig.getAppSecret();
            final String key = String.format(RedisKey.OPENID_GET_APPID, officialAccountOpenId);
            JSONObject appJsonObject = (JSONObject) redisCacheTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(appJsonObject)) {
                String appIdByIn = appJsonObject.getString("appId");
                if (!StringUtils.isEmpty(appIdByIn)) {
                    appId = appIdByIn;
                }

                String appSecretByIn = appJsonObject.getString("appSecret");
                if (!StringUtils.isEmpty(appIdByIn)) {
                    appSecret = appSecretByIn;
                }
            }

            String accessTokenIndateUrl = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appId, appSecret, code);
            String accessTokenIndateResult = HttpServiceUtil.httpGet(accessTokenIndateUrl);
            log.info("accessTokenIndateResult: {}", accessTokenIndateResult);

            JSONObject jsonObject = JSONObject.parseObject(accessTokenIndateResult);

            String userInfoUrl = String.format("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN", jsonObject.getString("access_token"), jsonObject.getString("openid"));
            String userInfo = HttpServiceUtil.httpGet(userInfoUrl);
            log.info("默认userInfo: {}", userInfo);

            /**
             * {"openid":"oIc_V57vlRdv6pc5QnqkSq61DFuM","nickname":"᭄赵᭄ᩚ刚💗এ华ꦿ13782616925","sex":0,"language":"","city":"","province":"","country":"","headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/YT2QHmMsibFiabjK2ibva5QbtibWxQIEUqLl42ZL7DHI2QC0iasTuY3y9fNQDL1bQL4av8HCkVEx35CFq4dqO4DBqqQ\/132","privilege":[],"unionid":"oCJdU1fFyc8h7xiYW88NjAKBqwrk"}:
             * {"openid":"oufus5_Ie8umWrV7H_mBhkav6sF4","nickname":"Mr Chen","sex":0,"language":"","city":"","province":"","country":"","headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/WCp4j6rR4jXRpbAtnDiaEuYmmT3JIFlxOuP8Jl66VNXC0LQ30PRxA43zbQZV4FK7jvbVpKEyluf8IoMrMYwfkKA\/132","privilege":[],"unionid":"oCJdU1YVJPCDyQHR6kGII1G0t6nA"}:
             *
             * {"errcode":48001,"errmsg":"api unauthorized, rid: 665463a7-4e6989e0-0d051cd2"}:
             */
            JSONObject userInfoJsonObject = JSONObject.parseObject(userInfo);
            final String errcode = userInfoJsonObject.getString("errcode");
            if (!StringUtils.isEmpty(errcode)) {
                JSONObject ui = new JSONObject();
                ui.put("openid", officialAccountOpenId);
                // 获取用户信息失败，黄远东的手机微信会这样{"errcode":48001,"errmsg":"api unauthorized, rid: 665463a7-4e6989e0-0d051cd2"}
                // {"errcode":48001,"errmsg":"api unauthorized, rid: 665463a7-4e6989e0-0d051cd2"}
                userInfo = JSON.toJSONString(ui);
                log.info("默认userInfo");
            } else {
                log.info("获取userInfo成功");
            }
            StringBuffer url = new StringBuffer(redirectUrl);
            url.append("&userInfo=").append(URLEncoder.encode(userInfo, "UTF-8"));

            return "redirect:" + url;
        } catch (Exception e) {
            e.printStackTrace();
            errorLogCommonService.writerInfoException("登录失败_4", null, e);
            model.addAttribute("msg", "登录失败_4，失败原因：" + e.getMessage());
            return "error";
        }
    }


    public static void main(String[] args) throws UnsupportedEncodingException {
        // 解码
        String decode = URLDecoder.decode("%B7%B7%B7%B7%B7%B7", "utf-8");
        System.out.println(decode);
        String decode2 = URLDecoder.decode("{\"headimgurl\":\"https://tfs.alipayobjects.com/images/partner/ATSLZaQp0V4B8AAAAAAAAAAAAADtl2AA\",\"userid\":\"2088232528834784\",\"nickname\":\"昊\",\"city\":\"\",\"province\":\"\"}", "utf-8");
        System.out.println(decode2);
//
//
//        // 编码
//        String encode = URLEncoder.encode("https://search.jd.com/search?keyword=%E6%89%8B%E6%9C%BA&wq=%E6%89%8B%E6%9C%BA&ev=3751_90887%5E5_257520%5E1012_122926%5E", "utf-8");
//        System.out.println(encode);
//        System.out.println(URLDecoder.decode(encode, "utf-8"));
        /**
         * https://search.jd.com/search?keyword=%E6%89%8B%E6%9C%BA&wq=%E6%89%8B%E6%9C%BA&ev=3751_90887%5E5_257520%5E1012_122926%5E
         * https://search.jd.com/search?keyword=%E6%89%8B%E6%9C%BA&wq=%E6%89%8B%E6%9C%BA&ev=3751_90887%5E5_257520%5E1012_122926%5E
         * https%3A%2F%2Fsearch.jd.com%2Fsearch%3Fkeyword%3D%25E6%2589%258B%25E6%259C%25BA%26wq%3D%25E6%2589%258B%25E6%259C%25BA%26ev%3D3751_90887%255E5_257520%255E1012_122926%255E
         */
//        System.out.println(URLEncoder.encode(decode, "UTF-8"));

//        String attach = "600032^0";
//        String attach = "600032_0";
////        String attach = request.getParameter("attach");
//        log.info("attach:{}", attach);
//        String decodeBeforeAttach = URLDecoder.decode(attach, "utf-8");
//        String[] attachArray = decodeBeforeAttach.split("\\^");
//        log.info("attachArray:{}", Arrays.toString(attachArray));
//        log.info("attachArray:{}", attachArray.length);
//        if (decodeBeforeAttach.contains("_")) {
//            decodeBeforeAttach = decodeBeforeAttach.replace("_", "^");
//            attachArray = decodeBeforeAttach.split("\\^");
//            log.info("attachArray_2:{}", Arrays.toString(attachArray));
//            log.info("attachArray_2:{}", attachArray.length);
//        }
//
//        Integer paramCode = Integer.parseInt(attachArray[1]);
//        log.info("paramCode:{}", paramCode);
//        for (int i = 0; i < 100; i++) {
//            System.out.println(Util.getOutTradeNo());
//        }
    }

    /**
     * 获取到用户信息回调
     *
     * @param request
     * @param model
     * @return
     */
    @Override
    public synchronized String login(HttpServletRequest request, Model model) {
        try {
            String memberInfoJSON = request.getParameter("userInfo");
            log.info("userInfo:{}", memberInfoJSON);

            memberInfoJSON = URLDecoder.decode(memberInfoJSON, "utf-8");
            log.info("userInfo:{}", memberInfoJSON);

            if (StringUtils.isEmpty(memberInfoJSON)) {
                errorLogCommonService.writerInfoErrorLog("获取会员信息失败，请重新扫码", null);
                model.addAttribute("msg", "获取会员信息失败，请重新扫码");
                return "error";
            }
            String attach = request.getParameter("attach");
            log.info("attach:{}", attach);
            String decodeBeforeAttach = URLDecoder.decode(attach, "utf-8");
            String[] attachArray = decodeBeforeAttach.split("\\^");
            log.info("attachArray:{}", Arrays.toString(attachArray));
            log.info("attachArray:{}", attachArray.length);
            if (decodeBeforeAttach.contains("_")) {
                decodeBeforeAttach = decodeBeforeAttach.replace("_", "^");
                attachArray = decodeBeforeAttach.split("\\^");
                log.info("attachArray_2:{}", Arrays.toString(attachArray));
                log.info("attachArray_2:{}", attachArray.length);
            }

            Integer paramCode = Integer.parseInt(attachArray[1]);
            log.info("paramCode:{}", paramCode);
            if (attachArray.length < 2) {
                model.addAttribute("msg", "参数有误");
                errorLogCommonService.writerInfoErrorLog("参数有误", attachArray);
                return "error";
            }

            String hierarchy = null;
            String freightLane = null;
            if (attachArray.length == 4) {
                hierarchy = attachArray[2];
                freightLane = attachArray[3];
                log.info("hierarchy:{}", hierarchy);
                log.info("freightLane:{}", freightLane);
            }

            String deviceNo = attachArray[0];

            JSONObject jsonObject = JSON.parseObject(memberInfoJSON);
            // keyWord既可以临时存openid，也可以临时存userid（支付宝的userid）
            final String officialAccountOpenId = jsonObject.getString("openid");
            final String unionId = jsonObject.getString("unionid");
            final String scope = jsonObject.getString("scope");
            final String zfbUserId = jsonObject.getString("userid");

            /**
             * 小程序使用记录，unionId存在，officialAccountOpenId不存在
             * 公众号静默授权，unionId不存在，officialAccountOpenId存在
             * 新用户，       unionId、officialAccountOpenId都不存在
             */

            Member memberInfo = null;
            if (StringUtils.isEmpty(officialAccountOpenId) && StringUtils.isEmpty(unionId) && StringUtils.isEmpty(zfbUserId)) {
                log.error("都不命中_1");
                errorLogCommonService.writerInfoErrorLog("会员信息获取失败_1", memberInfoJSON);
                model.addAttribute("msg", "会员信息获取失败_1");
                return "error";
            } else if (!StringUtils.isEmpty(zfbUserId)) {
                log.info("命中支付宝的userId:" + zfbUserId);
                memberInfo = memberService.getMemberByKeyWordByALiUnionId(zfbUserId);
            } else if (!StringUtils.isEmpty(unionId)) {
                log.info("命中微信的unionId:" + unionId);
                memberInfo = memberService.getMemberByWeChatUnionId(unionId);
                if (StringUtils.isEmpty(memberInfo)) {
                    // 预防openID已存在，只是没有unionId
                    memberInfo = memberService.getMemberByWeChatOfficialAccountOpenId(officialAccountOpenId);

                    if (!StringUtils.isEmpty(memberInfo)) {
                        // openID已存在，只是没有unionId，这里把unionId更新到数据库即可
                        Member m = new Member();
                        m.setId(memberInfo.getId());
                        m.setUnionId(unionId);
                        m.setAcquiredInfo(true);
                        memberService.updateById(m);
                        memberInfo.setUnionId(unionId);
                    }
                } else {
                    Member m = new Member();
                    m.setId(memberInfo.getId());
                    m.setOfficialAccountOpenId(officialAccountOpenId);
                    m.setAcquiredInfo(true);
                    m.setUserAgreement(true);
                    m.setPersonalProtectionPolicy(true);
                    memberService.updateById(m);
                }
            } else if (!StringUtils.isEmpty(officialAccountOpenId)) {
                log.info("命中微信的officialAccountOpenId:" + officialAccountOpenId);

                Member memberCondition = new Member();
                memberCondition.setOfficialAccountOpenId(officialAccountOpenId);
                List<Member> memberList = memberService.findAllMemberByCondition(memberCondition);
                if (!StringUtils.isEmpty(memberList)) {
                    if (memberList.size() == 1) {
                        memberInfo = memberList.get(0);
                    } else {
                        for (Member member : memberList) {
                            if (!StringUtils.isEmpty(member.getUnionId())) {
                                memberInfo = member;
                                log.info("根据officialAccountOpenId：{}命中unionId：{}", officialAccountOpenId, memberInfo.getUnionId());
                            }
                        }

                        if (StringUtils.isEmpty(memberList)) {
                            memberInfo = memberList.get(0);
                            log.warn("多个相同的officialAccountOpenId都无法命中unionId，最终默认memberId:{}", memberInfo.getId());
                        }
                    }
                }
            } else {
                log.error("都不命中_2");
                errorLogCommonService.writerInfoErrorLog("会员信息获取失败_2", memberInfoJSON);
                model.addAttribute("msg", "会员信息获取失败_2");
                return "error";
            }

            if (StringUtils.isEmpty(memberInfo)) {
                /**
                 * 支付宝
                 * {
                 *   "headimgurl": "https://tfs.alipayobjects.com/images/partner/T1ZT0CXltXXXXXXXXX",
                 *   "userid": "2088712121711201",
                 *   "nickname": "(ಡωಡ)hiahiahia ",
                 *   "city": "佛山市",
                 *   "province": "广东省"
                 * }
                 *
                 * 微信
                 * {
                 *   "openid": "oi4DO59qf4g2BnTbjZaNENP350dc",
                 *   "nickname": "C Q W",
                 *   "sex": 1,
                 *   "language": "zh_CN",
                 *   "city": "广州",
                 *   "province": "广东",
                 *   "country": "中国",
                 *   "headimgurl": "https://thirdwx.qlogo.cn/mmopen/vi_32/wonzX0yIia5PonfWACWb1p4RqZqs0yBDy1ib9VicUtkXV9xAVpMQFianDkCO3F5uSd31UV13G8FMLMbdZ6a2rtWS5w/132",
                 *   "privilege": [],
                 *   "unionid": "oCJdU1YVJPCDyQHR6kGII1G0t6nA"
                 * }
                 */

                memberInfo = new Member();
                String nickname = jsonObject.getString("nickname");
                if (StringUtils.isEmpty(nickname)) {
                    nickname = randomNickName();
                }
                memberInfo.setNickName(nickname);

                if (!StringUtils.isEmpty(officialAccountOpenId)) {
                    // 微信登录
                    memberInfo.setOfficialAccountOpenId(officialAccountOpenId);
                    memberInfo.setSex(jsonObject.getInteger("sex"));
                } else {
                    // 支付宝登录
                    memberInfo.setZfbUserId(zfbUserId);
                    memberInfo.setAcquiredInfo(true);
                }

                if (!StringUtils.isEmpty(unionId)) {
                    // 获取到unionId
                    memberInfo.setUnionId(unionId);
                    memberInfo.setAcquiredInfo(true);
                }

                memberInfo.setCountry(jsonObject.getString("country"));
                memberInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
                memberInfo.setStatus(MemberStatusEnum.NORMAL.getCode());
                log.info(JSON.toJSONString(memberInfo));

                memberService.addMember(memberInfo);
            } else {
                // 更新会员信息
                String nickName = jsonObject.getString("nickname");
                String headImgUrl = jsonObject.getString("headimgurl");
                this.updateMemberInfo(memberInfo, nickName, headImgUrl);
            }

            // 检查设备是否有记录
            DeviceVo deviceVo = deviceService.findOneDeviceByDeviceNo(deviceNo);
            if (StringUtils.isEmpty(deviceVo)) {
                model.addAttribute("msg", "设备不存在！");
                return "error";
            }

            String routeUrl = configProperties.getClientFrontEndUrl();
            if (!StringUtils.isEmpty(deviceVo.getAgencyId())) {
                final Agency agency = agencyService.getAgencyById(deviceVo.getAgencyId());
                if (!StringUtils.isEmpty(agency) && !StringUtils.isEmpty(agency.getDedicatedOfficialAccount())) {
                    // 合作伙伴私有化部署
                    final AppletOrOfficialAccount appletOrOfficialAccount = appletOrOfficialAccountCommonService.findOneById(agency.getDedicatedOfficialAccount());
                    if (!StringUtils.isEmpty(appletOrOfficialAccount)) {
                        routeUrl = appletOrOfficialAccount.getOfficialAccountUrl() + "/client";
                    }
                }
            }
            StringBuilder frontEndUrl = new StringBuilder(routeUrl);

            // 超级兑币机调试投币
            if (ApiParamEnum.CLIENT_COIN_CHANGER_DEBUG_INSERT_COIN.getCode().compareTo(paramCode) == 0) {
                if (!DeviceStatusEnum.NON_ACTIVATED.getCode().equals(deviceVo.getStatus()) &&
                        !DeviceStatusEnum.UNBIND.getCode().equals(deviceVo.getStatus())
                ) {
                    model.addAttribute("msg", "该功能只允许在设备未绑定时使用");
                    return "error";
                }
                /**
                 * 这里模拟账户密码登录 颁发token
                 */
                final String token = this.generateToken(memberInfo);

                // 超级兑币机先跳“运营商绑定”————>“设备调试”或“设置套餐”
                frontEndUrl.append("/#/pages-other/device-debug/smart-marketing-coin-changer-debug/smart-marketing-coin-changer-debug?")
                        .append("token=").append(token)
                        .append("&deviceNo=").append(deviceNo);
                return "redirect:" + frontEndUrl;
            } else if (ApiParamEnum.VENDMACH_ANDROID.getCode().compareTo(paramCode) == 0) {
                /**
                 * 这里模拟账户密码登录 颁发token
                 */
                final String token = this.generateToken(memberInfo);

                frontEndUrl.append("/#/pages-pay/android-vendmach-pay/android-vendmach-pay");
                frontEndUrl.append("?att=").append(attach);
                frontEndUrl.append("&token=").append(token);
                frontEndUrl.append("&paramCode=").append(paramCode);
                frontEndUrl.append("&deviceNo=").append(deviceNo);
                return "redirect:" + frontEndUrl;

            } else if (ApiParamEnum.CAMERAS_BUY_MERCHANDISE.getCode().compareTo(paramCode) == 0) {
                /**
                 * 这里模拟账户密码登录 颁发token
                 */
                final String token = this.generateToken(memberInfo);
                frontEndUrl.append("/#/pages-pay/camera-pay/camera-pay");
                Integer commodityId = Integer.parseInt(attachArray[2]);
                frontEndUrl.append("?commodityId=").append(commodityId);
                frontEndUrl.append("&token=").append(token);
                frontEndUrl.append("&deviceNo=").append(deviceNo);
                return "redirect:" + frontEndUrl;
            }

            if (ObjectUtils.isEmpty(deviceVo.getMerchantStoreId()) || deviceVo.getMerchantStoreId() == -1) {
                if (ApiParamEnum.CLIENT_ENTRANCE_OF_EXCHANGE_MALL.getCode().compareTo(paramCode) == 0) {
                    frontEndUrl.append("/#/pages-other/device-not-binding/device-not-binding");
                    return "redirect:" + frontEndUrl;
                }

                // 通用装机调试页面
                if (DeviceStatusEnum.NON_ACTIVATED.getCode().equals(deviceVo.getStatus())) {
                    Integer count = (Integer) redisCacheTemplate.opsForValue().get(String.format(RedisKey.DEBUG_CONSUME_SUM, deviceNo));
                    if (ObjectUtils.isEmpty(count)) {
                        count = 30;
                        redisCacheTemplate.opsForValue().set(String.format(RedisKey.DEBUG_CONSUME_SUM, deviceNo), count);
                    }
                    /**
                     * 这里模拟账户密码登录 颁发token
                     */
                    final String token = this.generateToken(memberInfo);

                    if (
                            DeviceTypeEnum.VENDING_MACHINE.getCode().equals(deviceVo.getDeviceTypeId())
                                    || DeviceTypeEnum.SCREEN_VENDING_MACHINE.getCode().equals(deviceVo.getDeviceTypeId())
                                    || DeviceTypeEnum.OPEN_DOOR_VENDING_MACHINE.getCode().equals(deviceVo.getDeviceTypeId())
                                    || DeviceTypeEnum.BLEND_VENDING_MACHINE.getCode().equals(deviceVo.getDeviceTypeId())
                    ) {
                        frontEndUrl.append("/#/pages-other/channel-management/channel-management?");
                    } else {
                        frontEndUrl.append("/#/pages-other/device-debug/device-debug?");
                    }
                    frontEndUrl.append("times=").append(count);
                    frontEndUrl.append("&token=").append(token);
                    frontEndUrl.append("&deviceNo=").append(deviceNo);

                    if (!StringUtils.isEmpty(hierarchy)){
                        frontEndUrl.append("&h=").append(hierarchy);
                    }
                    if (!StringUtils.isEmpty(freightLane)){
                        frontEndUrl.append("&f=").append(freightLane);
                    }

                    return "redirect:" + frontEndUrl;
                } else {
                    frontEndUrl.append("/#/pages-other/device-not-binding/device-not-binding");
                    return "redirect:" + frontEndUrl;
                }
            }

            // 如果该设备绑定了门店就可以给用户生成余额记录
            MemberStoreBalanceDto memberStoreBalance = new MemberStoreBalanceDto();
            memberStoreBalance.setDeviceNo(deviceNo);
            memberStoreBalance.setMemberId(memberInfo.getId());
            MemberStoreBalanceVo memberStoreBalanceVo = memberStoreBalanceService.findMemberStoreBalanceByDeviceNoAndMemberId(memberStoreBalance);
            if (ObjectUtils.isEmpty(memberStoreBalanceVo)) {
                memberStoreBalanceCommonService.add(memberInfo.getId(), deviceVo.getMerchantStoreId(), deviceVo.getUserId(), deviceNo);
                memberStoreBalanceVo = memberStoreBalanceService.findMemberStoreBalanceByDeviceNoAndMemberId(memberStoreBalance);
            }


//        // 超级兑币机扫码登录取币
//        if (ApiParamEnum.MEMBER_AUTH.getCode().compareTo(paramCode) == 0) {
//            final String token = this.generateToken(memberInfo);
//            frontEndUrl.append("/#/pages/coin-changer/member-auth/member-auth?token=" + token);
//            return "redirect:" + frontEndUrl;
//        }
//
//        if (ApiParamEnum.CLIENT_COIN_CHANGER_PURCHASE_PACKAGE.getCode().compareTo(paramCode) == 0) {
//            // 超级兑币机购买某个套餐入口
//            if (StringUtils.isEmpty(attachArray[2])) {
//                model.addAttribute("msg", "套餐参数有误");
//                return "error";
//            }
//            Long discountPackageId = Long.parseLong(attachArray[2]);
//            CommodityStoreUpDownVo commodityStoreUpDownVo = commodityStoreUpDownService.queryById(discountPackageId);
//            if (deviceVo.getMerchantStoreId().compareTo(commodityStoreUpDownVo.getMerchantStoreId()) != 0) {
//                model.addAttribute("msg", "找不到套餐");
//                return "error";
//            }
//
//            final String token = this.generateToken(memberInfo);
//            log.info("超级兑币机购买某个套餐入口token:" + token);
//            frontEndUrl.append("/#/pages/coin-changer/purchase-package/purchase-package");
//            frontEndUrl.append("?token=" + token);
//            frontEndUrl.append("&discountPackageId=" + attachArray[2]);
//            frontEndUrl.append("&coins=" + (commodityStoreUpDownVo.getRechargeCoin() + commodityStoreUpDownVo.getGiveCoin()));
//            frontEndUrl.append("&package_sell=" + String.valueOf(commodityStoreUpDownVo.getRetailPrice().setScale(2, BigDecimal.ROUND_HALF_UP)));
//            frontEndUrl.append("&deviceNo=" + deviceVo.getDeviceNo());
//            frontEndUrl.append("&merchantStoreId=" + deviceVo.getMerchantStoreId());
//            return "redirect:" + frontEndUrl;
//        }

//        if (ApiParamEnum.CLIENT_ENTRANCE_OF_EXCHANGE_MALL.getCode().compareTo(paramCode) == 0) {
//            // 兑换商城入口
//            MerchantStoreDto merchantStoreDto = new MerchantStoreDto();
//            merchantStoreDto.setId(deviceVo.getMerchantStoreId());
//            MerchantStoreVo store = merchantStoreService.findOneMerchantStoreByCondition(merchantStoreDto);
//
//            // 这里模拟账户密码登录 颁发token
//            final String token = this.generateToken(memberInfo);
//            log.info("兑换商城入口token:" + token);
//            if (!StringUtils.isEmpty(store.getIsExchange()) && store.getIsExchange()) {
//                frontEndUrl.append("/#/pages/tabBar/home/home");
//            } else {
//                model.addAttribute("msg", "商家未开启兑换商城");
//                return "error";
//            }
//            frontEndUrl.append("?token=" + token);
//            frontEndUrl.append("&deviceNo=" + deviceVo.getDeviceNo());
//            frontEndUrl.append("&userId=" + deviceVo.getUserId());
//            frontEndUrl.append("&merchantStoreId=" + deviceVo.getMerchantStoreId());
//            return "redirect:" + frontEndUrl;
//        }

            frontEndUrl.append("/#/pages/transfer-station/transfer-station");
            /**
             * 这里模拟账户密码登录 颁发token
             */
            final String token = this.generateToken(memberInfo);
            frontEndUrl.append("?token=" + token);
            frontEndUrl.append("&deviceNo=" + deviceNo);

            if(!StringUtils.isEmpty(hierarchy) && !StringUtils.isEmpty(freightLane)){
                frontEndUrl.append("&h=");
                frontEndUrl.append(hierarchy);
                frontEndUrl.append("&f=");
                frontEndUrl.append(freightLane);
            }

            return "redirect:" + frontEndUrl;
        } catch (Exception e) {
            e.printStackTrace();
            errorLogCommonService.writerInfoException("登录失败_5", null, e);
            model.addAttribute("msg", "登录失败_5，失败原因：" + e.getMessage());
            return "error";
        }
    }


    /**
     * 微信小程序登录
     *
     * @param weiXinDto
     * @param request
     * @return
     */
    @Override
    public ServerResponse appletLogin(WeiXinDto weiXinDto, HttpServletRequest request) {

        long begin = System.currentTimeMillis();
        log.info("--------begin----------" + begin);

        if (StringUtils.isEmpty(weiXinDto.getAppId())) {
            return ServerResponse.createByErrorMessage("APPID不能为空，请及时更新小程序");
        }

        log.info("微信小程序APPID：{}", weiXinDto.getAppId());
        final AppletOrOfficialAccount appletOrOfficialAccount = appletOrOfficialAccountCommonService.findOneByWeChatAppId(weiXinDto.getAppId());
        if (StringUtils.isEmpty(appletOrOfficialAccount)) {
            return ServerResponse.createByErrorMessage("未找到微信小程序信息，APPID：" + weiXinDto.getAppId());
        }

        final String jsCode2SessionJson = weChatAppletService.getJsCode2Session(weiXinDto.getCode(), appletOrOfficialAccount.getWeChatAppId(), appletOrOfficialAccount.getWeChatAppSecret());
        if (StringUtils.isEmpty(jsCode2SessionJson)) {
            return ServerResponse.createByErrorMessage("登录不成功");
        }

        final JSONObject jsonObject = JSON.parseObject(jsCode2SessionJson);
        log.info(jsonObject.toJSONString());
        /**
         * {
         *   "unionid": "oCJdU1Y****I1G0t6nA",
         *   "openid": "oROAp45T0******8Jgb_3UGs9Y",
         *   "session_key": "ZWP+*****t4lWIg=="
         * }
         */

        long end = System.currentTimeMillis();
        log.info("--------end----------" + end);

        final String unionId = jsonObject.getString("unionid");
        final String openId = jsonObject.getString("openid");
        log.info("unionId:{}", unionId);
        log.info("openId:{}", openId);

        Member memberInfo;
        if (!StringUtils.isEmpty(unionId)) {
            log.info("--------进入unionId处理----------");
            memberInfo = memberService.getMemberByWeChatUnionId(unionId);
            if (!StringUtils.isEmpty(memberInfo) && StringUtils.isEmpty(memberInfo.getOpenId())) {
                Member m = new Member();
                m.setId(memberInfo.getId());
                m.setOpenId(openId);
                memberService.updateById(m);
            }
        } else if (!StringUtils.isEmpty(openId)) {
            log.info("--------进入openId处理----------");
            memberInfo = memberService.getMemberByWeChatOpenId(openId);
        } else {
            log.info("--------进入报错处理----------");
            return ServerResponse.createByErrorMessage("获取用户信息失败");
        }

        if (StringUtils.isEmpty(memberInfo)) {
            memberInfo = new Member();
            memberInfo.setNickName("微信用户");
            memberInfo.setOpenId(openId);
            memberInfo.setUnionId(unionId);
            memberInfo.setHeadImgUrl(imageProperties.getStaticFileUrl() + "/image/common/member-header-image-default.svg");
            memberInfo.setStatus(MemberStatusEnum.NORMAL.getCode());
            memberInfo.setFirstUser(true);
            log.info(JSON.toJSONString(memberInfo));
            memberService.addMember(memberInfo);
        }

        final String token = this.generateToken(memberInfo);

        MemberVo memberVo = new MemberVo();
        memberVo.setToken(token);
        memberVo.setId(memberInfo.getId());
        memberVo.setNickName(memberInfo.getNickName());
        memberVo.setHeadImgUrl(memberInfo.getHeadImgUrl());
        memberVo.setMobile(memberInfo.getMobile());
        memberVo.setFirstUser(memberInfo.getFirstUser());
        if (StringUtils.isEmpty(memberInfo.getFetchCoinPwd())) {
            memberVo.setHaveFetchCoinPwd(false);
        } else {
            memberVo.setHaveFetchCoinPwd(true);
        }
        final String key = String.format(RedisKey.MEMBER_INFO, memberInfo.getId());
        redisCacheTemplate.delete(key);

        appletOrOfficialAccountCommonService.dedicatedExistConsume(appletOrOfficialAccount, memberInfo);
        return ServerResponse.createBySuccess(memberVo);
    }


    /**
     * 会员信息更新
     *
     * @param memberInfo 数据库存的会员信息
     * @param nickName   会员最新昵称
     * @param headImgUrl 会员最新头像
     */
    private void updateMemberInfo(Member memberInfo, String nickName, String headImgUrl) {
        // 更新会员信息，及时更新会员的名称和头像信息
        try {

            Member m = null;
            if (!StringUtils.isEmpty(headImgUrl) && !headImgUrl.equals(memberInfo.getHeadImgUrl())) {
                m = new Member();
                m.setId(memberInfo.getId());
                m.setHeadImgUrl(headImgUrl);
            }
            if (!StringUtils.isEmpty(nickName) && (StringUtils.isEmpty(memberInfo.getNickName()) || !memberInfo.getNickName().equals(nickName))) {
                if (StringUtils.isEmpty(m)) {
                    m = new Member();
                    m.setId(memberInfo.getId());
                }
                m.setNickName(nickName);
            }

            if (!StringUtils.isEmpty(m)) {
                m.setAcquiredInfo(true);
                memberService.updateById(m);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 这里模拟账户密码登录 颁发token
     *
     * @param memberInfo
     * @return
     */
    private String generateToken(Member memberInfo) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(memberInfo, null, null);
        SecurityContextHolder.getContext()
                .setAuthentication(authentication);
        final String randomKey = jwtTokenUtil.getRandomKey();
        final String token = jwtTokenUtil.generateToken(memberInfo.getId().toString(), randomKey);

        // 存到redis
        stringRedisTemplate.opsForValue().set(JwtRedisEnum.getTokenKey(memberInfo.getId().toString(), randomKey), token, securityProperties.getJwt().getExpiration(), TimeUnit.SECONDS);

        // 将认证信息存到redis，方便后期业务用，也方便 JwtAuthenticationTokenFilter用
//        stringRedisTemplate.opsForValue().set(JwtRedisEnum.getAuthenticationKey(memberInfo.getId().toString(), randomKey), JSON.toJSONString(authentication), securityProperties.getJwt().getExpiration(), TimeUnit.SECONDS);

        return token;
    }

    /**
     * 生成随机名称
     *
     * @return
     */
    private String randomNickName() {
        String val = "";
        Random random = new Random();

        //参数length，表示生成几位随机数
        for (int i = 0; i < 8; i++) {

            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
            //输出字母还是数字
            if ("char".equalsIgnoreCase(charOrNum)) {
                //输出是大写字母还是小写字母
                int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
                val += (char) (random.nextInt(26) + temp);
            } else if ("num".equalsIgnoreCase(charOrNum)) {
                val += String.valueOf(random.nextInt(10));
            }
        }
        return "m_" + val;
    }
}
